package cn.xzqwjw.taskmanager.config;

import cn.xzqwjw.taskmanager.security.*;
import cn.xzqwjw.taskmanager.security.handler.AuthAccessDeniedHandler;
import cn.xzqwjw.taskmanager.security.handler.CustomLogoutHandler;
import cn.xzqwjw.taskmanager.security.handler.CustomLogoutSuccessHandler;
import cn.xzqwjw.taskmanager.security.handler.NotLoginEntryPoint;
import cn.xzqwjw.taskmanager.service.LogLoginService;
import cn.xzqwjw.taskmanager.service.SysAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

/**
 * @author rush
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  private final SysAdminService adminService;
  private final AuthMetadataSource authMetadataSource;
  private final LogLoginService logLoginService;

  @Autowired
  public SecurityConfig(SysAdminService adminService,
                        LogLoginService logLoginService,
                        AuthMetadataSource authMetadataSource) {
    this.adminService = adminService;
    this.logLoginService = logLoginService;
    this.authMetadataSource = authMetadataSource;
  }

  @Override
  protected void configure(AuthenticationManagerBuilder authBuilder) {
    authBuilder
        .authenticationProvider(new JwtAuthProvider())
        .authenticationProvider(new LoginAuthAdminProvider(adminService, logLoginService));
  }

  /**
   * SpringbootSecurity核心配置
   */
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // String[] aryAllowHeader = {
    //   "Access-Control-Allow-Headers",
    //   "Authorization, x-xsrf-token, Access-Control-Allow-Headers, Origin, Accept,
    //   X-Requested-With",
    //   "Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers"
    // };
    // List<String> listAllowHeader = Arrays.asList(aryAllowHeader);

    // 禁用 session
    http.sessionManagement().disable();
    // 因为不使用 session，所以要禁用 Spring Security 自带的跨域处理，关闭跨站跨域请求
    // 显式禁止掉了 CORS、CSRF ，因为默认 Spring Security 已经支持了
    // 如果不去掉则 Response 中可能会出现多个 Access-Control-Allow-Origin 头的问题导致浏览器报错
    // The‘Access-Control-Allow-Origin’header contains multiple values‘*,*’,but only one is allowed.
    http.cors().and().csrf().disable();
    // 关闭X-Frame-Options，解决不允许显示在iframe的问题
    http.headers().frameOptions().disable();
    // 配置
    http.authorizeRequests()
        // .antMatchers("/", "/favicon.ico", "/html/**").permitAll()
        // .antMatchers("/static/**", "/upload-file/**").permitAll()
        // .antMatchers("/verify-code-image").permitAll()
        // 设置“/api-public/reader?action=check&phoneNumber=13403030123”这样的url不需要鉴权
        // .antMatchers(HttpMethod.GET, "/api-public/**").permitAll()
        // .antMatchers(HttpMethod.POST, "/api-public/**").permitAll()
        // .antMatchers(HttpMethod.PUT, "/api-public/**").permitAll()
        // .antMatchers(HttpMethod.PATCH, "/api-public/**").permitAll()
        // .antMatchers(HttpMethod.OPTIONS, "/api-public/**").permitAll()
        // 设置所有OPTIONS请求不需要鉴权
        // .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
        // 为swagger页面放行
        // .antMatchers("/swagger-ui/**").permitAll()
        // .antMatchers("/swagger-resources/**").permitAll()
        // .antMatchers("/v2/api-docs").permitAll()
        // .antMatchers("/v3/api-docs").permitAll()
        // .antMatchers("/webjars/springfox-swagger-ui/**").permitAll()
        // 下面这句是 url 资源动态匹配权限的核心语句
        // 先得到所有要鉴权的资源数组，然后拿现在要访问的url去比对
        .withObjectPostProcessor(getObjectPostProcessor())
        // 默认其它的请求都需要认证，这里一定要添加
        .anyRequest().authenticated()
        // .anyRequest().fullyAuthenticated()
        .and()
        // 设置登录、登出页不需要鉴权
        // .antMatchers("/api/admin/login", "/api/admin/logout").permitAll()
        // .antMatchers("/api/reader/login", "/api/reader/logout").permitAll()
        // 禁用表单登录方式
        .formLogin().disable()
        // 添加header设置，支持跨域和ajax请求
        // headers()
        // .addHeaderWriter(new StaticHeadersWriter(
        //   Arrays.asList(
        //     // 支持所有源的访问
        //     new Header("Access-control-Allow-Origin", "*"),
        //     // 使 ajax 请求能够取到header中的 jwt token 信息
        //     new Header("Access-Control-Expose-Headers", "authorization"))
        // ))
        // .and()
        // 拦截OPTIONS请求，直接返回 header
        // .addFilterBefore(new OptionsRequestFilter(), LogoutFilter.class)
        // 添加验证管理员登录 filter
        .apply(new LoginAuthAdminConfig<>(logLoginService)).and()
        // 添加验证每个请求是否带 JWT 的 filter
        .apply(new JwtAuthConfig<>())
        // 在 filter 里写一个自定义函数，注意这里是设置不需要 jwt 验证的 url
        .permissiveRequestUrls("/api/admin/login", "/api/admin/logout")
        .and()
        // 添加鉴权成功或者失败的自定义异常入口
        .exceptionHandling()
        // 处理匿名用户访问无权限资源时的异常
        .authenticationEntryPoint(new NotLoginEntryPoint())
        // 处理已登录（认证）用户访问无权限资源时的异常（权限不足，拒绝访问）
        .accessDeniedHandler(new AuthAccessDeniedHandler()).and()
        // 使用默认的 logout Filter
        // 如果我们实现了自定义的 LogoutSuccessHandler
        // 就不必设置 logoutSuccessUrl(String logoutSuccessUrl)
        .logout()
        .logoutUrl("/api/admin/logout")
        // logout时清除token
        .addLogoutHandler(new CustomLogoutHandler())
        // logout成功后返回200
        .logoutSuccessHandler(new CustomLogoutSuccessHandler());
  }

  /**
   * （已过时）这里主要用于访问一些静态的东西控制
   * 其中ignoring()方法可以让访问跳过filter验证，不需要权限即可访问
   */
  @Override
  public void configure(WebSecurity web) {
    // 定义哪些url不需要被拦截
    // ** 指的是匹配0或者更多的目录，注意是包含"/"
    web.ignoring()
        .antMatchers("/", "/favicon.ico", "/html/**")
        .antMatchers("/static/**", "/upload-file/**")
        .antMatchers("/verify-code-image")
        .antMatchers("/error/**", "/test/**");
  }

  private ObjectPostProcessor<FilterSecurityInterceptor> getObjectPostProcessor() {
    return new ObjectPostProcessor<>() {
      @Override
      public <O extends FilterSecurityInterceptor> O postProcess(O object) {
        // 先获取访问某个url所需的角色或者权限
        object.setSecurityMetadataSource(authMetadataSource);
        // 再用现在登录的用户有哪些权限来验证是否允许访问某个url
        object.setAccessDecisionManager(new AuthAccessDecisionManager());
        return object;
      }
    };
  }

}
